Skip to content

[WIP] Run FixLegacyResourceDesigner before trimming#11084

Open
sbomer wants to merge 16 commits intomainfrom
dev/sbomer/fix-legacy-before-trim
Open

[WIP] Run FixLegacyResourceDesigner before trimming#11084
sbomer wants to merge 16 commits intomainfrom
dev/sbomer/fix-legacy-before-trim

Conversation

@sbomer
Copy link
Copy Markdown
Member

@sbomer sbomer commented Apr 7, 2026

#11059 is moving this step to run after trimming.
This comes with an APK size regression because ILLink no longer has a chance to trim the shared designer assembly.
This change is exploring making it a pre-trim step instead.

sbomer added 11 commits March 30, 2026 16:55
Migrate FixLegacyResourceDesignerStep out of the ILLink trimmer process
and into PostTrimmingPipeline, continuing the work in #10842 to remove
custom ILLink steps. The step now runs after ILLink via a thin wrapper
(PostTrimmingFixLegacyResourceDesignerStep) that calls
ProcessAssemblyDesigner() directly, matching the former ILLink behavior.

- Remove all #if ILLINK conditionals from FixLegacyResourceDesignerStep,
  LinkDesignerBase, and BaseStep
- Remove FixLegacyResourceDesignerStep and LinkDesignerBase from the
  ILLink project (no longer compiled as a trimmer step)
- Add UseDesignerAssembly property to PostTrimmingPipeline task
- Wire AndroidUseDesignerAssembly through targets to PostTrimmingPipeline
- Remove _TrimmerCustomSteps entry for FixLegacyResourceDesignerStep
In NativeAOT builds, _AddResourceDesignerToPublishFiles ran after
_ComputeManagedAssemblyToLink, so the designer assembly was not in
ManagedAssemblyToLink. ILLink skipped the designer assembly entirely
and did not rewrite its netstandard references.

Fix by adding _AddResourceDesignerToPublishFiles to _PrepareLinking's
DependsOnTargets, so the designer is passed to ILLink.
# Conflicts:
#	src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj
FixAbstractMethodsStep was moved from ILLink to the post-trim pipeline
on main (7ae24aa). The merge conflict resolution incorrectly kept the
Compile include, but the file now uses types not available in the ILLink
project (IAssemblyModifierPipelineStep, StepContext, Properties.Resources).
…rties

FixLegacyResourceDesignerStep now runs in PostTrimmingPipeline (after ILLink)
instead of as an ILLink custom step. Previously, the step ran before MarkStep
and rewrote library assemblies to reference designer properties, which caused
MarkStep to preserve them. Now that the rewriting happens after ILLink, we
must root the designer assembly so all resource properties survive trimming.

Add TrimmerRootAssembly for the designer in _AddResourceDesignerToPublishFiles.
This keeps IsTrimmable=true (action=link) so ILLink still rewrites the
netstandard assembly reference, but roots all types so nothing is trimmed.

An alternative approach would be to run FixLegacyResourceDesignerStep before
ILLink instead of after. That would allow ILLink to trim unused resource
properties from the designer (since the rewritten references would already be
in place for MarkStep), but it would also process resource references in
assemblies that ILLink may later remove entirely.
… assembly

TrimmerRootAssembly (-a flag) makes the designer an entry point, which
causes ILLink to retain netstandard.dll as a dependency, leaking it into
the APK (+19KB). TrimmerRootDescriptor (-x flag) preserves all designer
types/members without making it an entry point, avoiding the regression.
…builds

FixLegacyResourceDesignerStep now runs in PostTrimmingPipeline (MSBuild
task) instead of inside ILLink, so the error code is always XA8000
regardless of build configuration.
WriteLinesToFile was regenerating the TrimmerRootDescriptor XML on every
build, changing its timestamp and causing ILLink to re-run on incremental
builds. Adding WriteOnlyWhenDifferent preserves the timestamp when the
content hasn't changed.
Run FixLegacyResourceDesignerStep *before* ILLink instead of after it,
so the trimmer sees the rewritten IL (call instructions to designer
property getters) and can freely trim unused designer types. This
removes the need to root the entire designer assembly via a
TrimmerRootDescriptor during trimming, eliminating the APK size
regression.
Copilot AI review requested due to automatic review settings April 7, 2026 17:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR explores running the legacy resource designer rewrite before ILLink so that ILLink can trim unused members from the shared resource designer assembly (avoiding the APK size regression introduced when moving the step post-trim in #11059).

Changes:

  • Add a new MSBuild task (PreTrimmingFixLegacyDesigner) and wire it to run before ILLink when trimming with AndroidUseDesignerAssembly=True.
  • Remove the legacy designer rewrite from ILLink custom steps and adjust linker/shared code to no longer compile under the ILLink project.
  • Update a device integration test expectation to always report XA8000 (instead of IL8000 in some cases).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs Updates expected error code to XA8000 now that the failure is reported outside ILLink.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj Ensures needed linker sources (incl. LinkDesignerBase) are compiled into build tasks.
src/Xamarin.Android.Build.Tasks/Tasks/PreTrimmingFixLegacyDesigner.cs Introduces the pre-trim task that rewrites assemblies in-place prior to ILLink.
src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets Documents why a root descriptor is no longer needed with the pre-trim rewrite approach.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets Wires the new pre-trim task before ILLink and removes the old ILLink custom step hook.
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs Removes ILLink-conditional logging paths now that this code no longer runs inside ILLink.
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs Makes the step consistently implement the build-task pipeline interface and removes ILLink-only code paths.
src/Xamarin.Android.Build.Tasks/Linker/External/Linker.Steps/BaseStep.cs Removes ILLink-conditional logging path to match non-ILLink usage.
src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj Stops compiling the legacy designer step into the ILLink tool assembly.

sbomer added 2 commits April 7, 2026 11:44
FixLegacyResourceDesignerStep now runs in PostTrimmingPipeline for all
runtimes including NativeAOT, so the missing @styleable/SKCanvasView
resources correctly produce XA8000 errors. Remove the NativeAOT
exclusion from the addResource=False path.
@sbomer
Copy link
Copy Markdown
Member Author

sbomer commented Apr 7, 2026

This content was created with assistance from AI.

The CI failures are caused by the same satellite assembly file-locking issue tracked in #11085: PreTrimmingFixLegacyDesigner passes all .dll files from @(ResolvedFileToPublish) and opens them with ReadWrite = true, which crashes on Windows when satellite assemblies (e.g., ar/Microsoft.Maui.Controls.resources.dll) in the shared NuGet cache are locked by parallel inner builds. Will fix by filtering on %(PostprocessAssembly) metadata once #11085 lands.

sbomer and others added 3 commits April 10, 2026 08:52
Both _PreTrimmingFixLegacyDesigner and _PostTrimmingPipeline were including
all .dll files from ResolvedFileToPublish, which includes satellite resource
assemblies (.resources.dll) from the shared NuGet package cache. These tasks
open assemblies with ReadWrite access via DirectoryAssemblyResolver, causing
IOException when parallel multi-RID builds contend for locks on the same
cached files.

Filter both targets to only process items where PostprocessAssembly metadata
is 'true', which excludes satellite assemblies that ILLink does not process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The resolver was using ReadWrite=true, which acquires exclusive file locks on
every assembly it loads — including the shared _Microsoft.Android.Resource.Designer.dll
that is loaded implicitly via Resolve() during FixLegacyResourceDesignerStep.LoadDesigner().

The designer DLL is built once in the outer build at a shared obj/ path, but
this task runs in each inner per-RID build. Parallel RID builds contend for
exclusive locks on the shared file, causing IOException.

Since the task only reads the designer assembly (never writes it), drop
ReadWrite=true so all assemblies are opened in shared read mode. Library
assemblies that get modified are written back using an explicit output path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants